﻿using System;
using System.Net.Sockets;
using System.Threading.Tasks;
using System.Threading;

/////////////////////////////
//  Gestion partie reseau  //
/////////////////////////////

namespace Strom.Core.Network
{
    public class BaseClient
    {
        private Socket _socket;
        public bool IsConnected { get; private set; }
        private byte _packetHeaderSize;
        private string _remoteEndPointString;
        public object Tag { get; set; }
        public Accounts.Account CurrentAccount { get; set; }
        public Characters.Character CurrentCharacter { get; set; }

        public delegate void PacketReceivedHandler(BaseClient client, Packet packet);
        public event PacketReceivedHandler PacketReceived;
        public delegate void ClientDisconnectedHandler(BaseClient client);
        public event ClientDisconnectedHandler ClientDisconnected;

        // Classe client ayant les paramètres d'un socket existant
        public BaseClient(Socket socket)
        {
            _socket = socket;
            IsConnected = _socket.Connected;

            if (IsConnected)
                StartReceive();
        }

        // Classe client vierge
        public BaseClient()
        {
            IsConnected = false;
        }

        // Connexion à un serveur
        public void Connect(string ip, int port)
        {
            if (IsConnected) return;

            if (_socket == null)
                _socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);

            _socket.Connect(ip, port);
            IsConnected = true;
            StartReceive();
        }

        // Deconnexion du client
        public void Disconnect()
        {
            if(!IsConnected) return;

            IsConnected = false;

            if (_socket == null || !_socket.Connected) return;

            lock (_socket)
            {
                _socket.Shutdown(SocketShutdown.Both);
                _socket.Close();
                _socket = null;
            }

            if (CurrentAccount != null)
                CurrentAccount.IsOnline = false;

            if (ClientDisconnected != null)
                ClientDisconnected(this);
        }

        // Debut de la réception asynchrone
        void StartReceive()
        {
            _packetHeaderSize = Packet.GetHeaderLenght();
            _remoteEndPointString = _socket.RemoteEndPoint.ToString();
            new Task(Receive).Start();
        }

        void Receive()
        {
            while (IsConnected)
            {
                Thread.Sleep(50);
                CheckIfClientIsDisconnected();
                if (!IsConnected) return;

                if (_socket.Available < _packetHeaderSize) continue;

                #region Reception du header
                Packet incomingPacket = GetIncomingPacket();
                var incomingPacketDatasBuffer = new byte[incomingPacket.DatasLenght];
                int read = 0;
                #endregion

                #region Reception de tout le corps du packet
                while (read != incomingPacket.DatasLenght && IsConnected)
                {
                    System.Threading.Thread.Sleep(50);

                    if (_socket.Available > 0)
                    {
                        int datasLeft = incomingPacket.DatasLenght - read;

                        #region Reception d'une partie du corps du packet
                        lock (_socket)
                        {
                            if (datasLeft > _socket.Available || datasLeft == _socket.Available)
                                _socket.Receive(incomingPacketDatasBuffer, read, _socket.Available,
                                                SocketFlags.None);
                            else
                                _socket.Receive(incomingPacketDatasBuffer, read, datasLeft, SocketFlags.None);
                        }
                        #endregion

                        read = incomingPacketDatasBuffer.Length;
                    }
                    else
                        CheckIfClientIsDisconnected();
                }
                #endregion

                if (!IsConnected)
                    return;

                #region Invocation de l'event packet reçu
                incomingPacket.SetDatas(incomingPacketDatasBuffer);
                ThreadPool.QueueUserWorkItem(InvokeThread, incomingPacket);
                #endregion
            }
        }

        void InvokeThread(object packet)
        {
            if (PacketReceived != null)
                PacketReceived(this, (Packet)packet);
        }

        // Envoi paquet
        public void Send(Packet packet)
        {
            if (!IsConnected) return;

            _socket.Send(packet.GetFullPacket());
        }

        // Récupération de l'entête du paquet entrant
        Packet GetIncomingPacket()
        {
            var header = new byte[_packetHeaderSize];
            _socket.Receive(header);
            return Packet.FromHeaderArray(header);
        }

        void CheckIfClientIsDisconnected()
        {
            if (_socket == null) return;

            if(_socket.Poll(-1, SelectMode.SelectRead) && _socket.Available == 0)
                Disconnect();
        }

        public override string ToString()
        {
            return string.Format("<{0}>", _remoteEndPointString);
        }
    }
}
